/**
 * @brief OpenGL实现的太阳系九大行星模拟程序，支持交互缩放、速度调节、暂停、停止等功能
 * @author cursor生成
 */

#include <GL/glut.h>

#include <algorithm>
#include <cmath>
#include <cstdlib>
#if __cplusplus >= 201703L
#include <numbers>
#endif
#include <string>
#include <utility>
#include <vector>
#ifdef __linux__
#include <X11/Xlib.h>
#endif

#include "stl/utility/ignored_unused.hpp"

const float ORBIT_LINE_WIDTH = 1.2F;  ///< 轨道线宽
const int   ORBIT_SEGMENTS   = 360;   ///< 轨道细分段数
// const int   PLANET_SLICES    = 60;     ///< 行星球体横向细分
// const int   PLANET_STACKS    = 60;     ///< 行星球体纵向细分
const int   MOON_SLICES    = 40;     ///< 月球球体横向细分
const int   MOON_STACKS    = 40;     ///< 月球球体纵向细分
const float ZOOM_MIN       = 0.3F;   ///< 最小缩放比例
const float ZOOM_MAX       = 3.0F;   ///< 最大缩放比例
const float ZOOM_STEP_IN   = 0.9F;   ///< 缩小步进
const float ZOOM_STEP_OUT  = 1.1F;   ///< 放大步进
const float BASE_ORTHO_X   = 2.5F;   ///< 正交投影X基准
const float BASE_ORTHO_Y   = 2.0F;   ///< 正交投影Y基准
const int   TIMER_INTERVAL = 16;     ///< 定时器间隔(ms)
const int   ESC_KEY        = 27;     ///< ESC键ASCII
const float SPEED_STEP     = 0.2F;   ///< 速度调节步进
const float SPEED_MIN      = 0.1F;   ///< 最小速度
const float SPEED_MAX      = 10.0F;  ///< 最大速度
#if __cplusplus >= 201703L
const float PI = std::numbers::pi_v<float>;
#else
const float PI = 3.1415926;
#endif

/**
 * @brief 轨道顶点缓存，全局唯一。
 */
std::vector<std::pair<float, float>> orbitCache;

/**
 * @brief 初始化轨道顶点缓存。
 * @param segments 轨道细分段数
 */
void initOrbitCache(int segments = ORBIT_SEGMENTS) {
    orbitCache.clear();
    for (int i = 0; i < segments; ++i) {
        float a = 2.0F * PI * i / segments;

        orbitCache.emplace_back(std::cos(a), std::sin(a));
    }
}

/**
 * 在指定位置绘制字符串
 *
 * @param x 指定字符串绘制的x坐标
 * @param y 指定字符串绘制的y坐标
 * @param str 指向要绘制的字符串的常量指针
 *
 * 本函数使用OpenGL的glRasterPos2f函数设置字符串绘制的位置，
 * 然后使用glutBitmapCharacter函数逐字符绘制字符串
 */
void drawString(float x, float y, const char* str) {
    // 设置字符串绘制的位置
    glRasterPos2f(x, y);

    // 遍历字符串中的每个字符，直到遇到字符串结束符
    while (*str != 0) {
        // 绘制当前字符，并将指针移动到下一个字符
        glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, *str++);
    }
}

/**
 * @brief 在窗口像素坐标绘制字符串。
 * @param x 左下角x像素
 * @param y 左下角y像素
 * @param str 字符串内容
 */
void drawStringPixel(int x, int y, const char* str) {
    int winW = glutGet(GLUT_WINDOW_WIDTH);
    int winH = glutGet(GLUT_WINDOW_HEIGHT);
    // 保存当前投影和模型视图矩阵
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluOrtho2D(0, winW, 0, winH);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glRasterPos2i(x, y);
    while (*str != 0) {
        glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, *str++);
    }
    // 恢复原矩阵
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
}

int getPlanetSlices(float zoom) {
    if (zoom < 0.5F) {
        return 20;
    }
    if (zoom < 1.0F) {
        return 40;
    }
    if (zoom < 2.0F) {
        return 60;
    }
    return 80;
}

/**
 * @brief 行星类，包含轨道参数、颜色、卫星等。
 */
class Planet {
public:
    /**
     * @brief 构造函数
     * @param name 行星名称
     * @param orbitRadius 轨道半径
     * @param size 行星半径
     * @param orbitSpeed 公转速度
     * @param r,g,b 颜色
     */
    Planet(std::string name, float orbitRadius, float size, float orbitSpeed, float r, float g, float b)
        : name(std::move(name)),
          orbitRadius(orbitRadius),
          size(size),
          orbitSpeed(orbitSpeed),
          angle(0.0F),
          r(r),
          g(g),
          b(b),
          selfAngle(0.0F),
          hasMoon(false) {}

    /**
     * @brief 设置卫星（月球）
     * @param moon 卫星指针
     * @param moonOrbitRadius 卫星轨道半径
     */
    void setMoon(Planet* moon, float moonOrbitRadius) {
        this->moon            = moon;
        this->moonOrbitRadius = moonOrbitRadius;
        hasMoon               = true;
    }

    /**
     * @brief 更新行星状态（自转、公转）
     */
    void update() {
        angle += orbitSpeed;
        if (angle > 360) {
            angle -= 360;
        }
        selfAngle += 2.0F;
        if (selfAngle > 360) {
            selfAngle -= 360;
        }
        if (hasMoon && (moon != nullptr)) {
            moon->update();
        }
    }

    /**
     * @brief 按指定速度更新行星状态
     * @param speed 速度倍数
     */
    void updateWithSpeed(float speed) {
        angle += orbitSpeed * speed;
        if (angle > 360) {
            angle -= 360;
        }
        selfAngle += 2.0F * speed;
        if (selfAngle > 360) {
            selfAngle -= 360;
        }
        if (hasMoon && (moon != nullptr)) {
            moon->updateWithSpeed(speed);
        }
    }

    /**
     * @brief 绘制轨道
     */
    void drawOrbit() const {
        glColor3f(0.5F, 0.5F, 0.5F);
        glLineWidth(ORBIT_LINE_WIDTH);
        glBegin(GL_LINE_LOOP);
        for (const auto& pt : orbitCache) {
            glVertex2f(pt.first * orbitRadius, pt.second * orbitRadius);
        }
        glEnd();
        glLineWidth(1.0F);
    }

    /**
     * @brief 绘制行星及其卫星
     */
    void draw() const {
        // 保存当前矩阵状态
        glPushMatrix();
        // 绕Z轴旋转一定角度
        glRotatef(angle, 0.0F, 0.0F, 1.0F);
        // 沿X轴平移一定距离，模拟行星围绕中心的轨道运动
        glTranslatef(orbitRadius, 0.0F, 0.0F);
        // 设置当前颜色
        glColor3f(r, g, b);
        // 绘制一个实心球体，参数分别为球体半径、经纬度分割数
        glutSolidSphere(size, getPlanetSlices(zoom), getPlanetSlices(zoom));

        // 如果当前行星有卫星且卫星不为空
        if (hasMoon && (moon != nullptr)) {
            // 设置绘制卫星的颜色为灰色
            glColor3f(0.7F, 0.7F, 0.7F);
            // 设置绘制轨道线的宽度
            glLineWidth(ORBIT_LINE_WIDTH);
            // 开始绘制卫星的轨道，使用线框模式
            glBegin(GL_LINE_LOOP);
            // 绘制轨道的循环，根据轨道分割数决定
            for (int i = 0; i < ORBIT_SEGMENTS; ++i) {
                // 计算轨道上每个点的角度
                float a = 2.0F * PI * i / ORBIT_SEGMENTS;
                // 根据角度计算并指定轨道上的顶点位置
                glVertex2f(std::cos(a) * moonOrbitRadius, std::sin(a) * moonOrbitRadius);
            }
            // 结束线框绘制
            glEnd();
            // 恢复绘制线宽为默认值
            glLineWidth(1.0F);
            // 保存当前矩阵状态，为绘制卫星做准备
            glPushMatrix();
            // 绕Z轴旋转卫星的角度
            glRotatef(moon->angle, 0.0F, 0.0F, 1.0F);
            // 沿X轴平移，模拟卫星围绕行星的轨道运动
            glTranslatef(moonOrbitRadius, 0.0F, 0.0F);
            // 设置绘制卫星的颜色
            glColor3f(moon->r, moon->g, moon->b);
            // 绘制卫星，参数分别为半径、经纬度分割数
            glutSolidSphere(moon->size, MOON_SLICES, MOON_STACKS);
            // 恢复矩阵状态，完成卫星的绘制
            glPopMatrix();
        }
        // 恢复矩阵状态，完成行星及其卫星的绘制
        glPopMatrix();
    }

    std::string name;             ///< 行星名称
    float       orbitRadius;      ///< 轨道半径
    float       size;             ///< 行星半径
    float       orbitSpeed;       ///< 公转速度
    float       angle;            ///< 当前公转角度
    float       r, g, b;          ///< 颜色
    float       selfAngle;        ///< 当前自转角度
    bool        hasMoon;          ///< 是否有卫星
    Planet*     moon = nullptr;   ///< 卫星指针
    float       moonOrbitRadius;  ///< 卫星轨道半径
    float       zoom;
};

/**
 * @brief 太阳系模拟主类，管理所有行星、交互、渲染。
 */
class SolarSystem {
public:
    static SolarSystem& getInstance() { return instance; }

    ~SolarSystem() {
        delete sun;
        for (auto* p : planets) {
            delete p;
        }
        delete moon;
    }
    /**
     * @brief 显示回调，负责所有渲染
     */
    void display() {
        glClear(GL_COLOR_BUFFER_BIT);
        glLoadIdentity();
        // 画太阳
        glColor3f(1.0F, 1.0F, 0.0F);
        glutSolidSphere(sun->size, 30, 30);
        // 画行星及其轨道
        for (auto* p : planets) {
            p->drawOrbit();
            p->draw();
        }
        // 绘制按键说明区域（固定在窗口左上角，像素坐标）
        // int winW       = glutGet(GLUT_WINDOW_WIDTH);
        int winH       = glutGet(GLUT_WINDOW_HEIGHT);
        int margin     = 30;
        int lineHeight = 22;
        glColor3f(1.0F, 1.0F, 1.0F);
        drawStringPixel(margin, winH - margin, "[Space]/P: Pause/Resume");
        drawStringPixel(margin, winH - margin - lineHeight, "S: Stop/Resume");
        drawStringPixel(margin, winH - margin - (2 * lineHeight), "+/-: Speed Up/Down");
        drawStringPixel(margin, winH - margin - (3 * lineHeight), "Mouse Wheel: Zoom");
        drawStringPixel(margin, winH - margin - (4 * lineHeight), "ESC: Exit");
        glutSwapBuffers();
    }

    /**
     * @brief 定时器回调，驱动动画
     */
    void update() {
        if (!stopped) {
            if (!paused) {
                for (auto* p : planets) {
                    p->updateWithSpeed(speed);
                }
            }
            glutPostRedisplay();
            glutTimerFunc(TIMER_INTERVAL, timerFunc, 0);
        }
    }
    /**
     * @brief 窗口尺寸变化回调
     * @param w 宽度
     * @param h 高度
     */
    void reshape(int w, int h) const {
        // 设置视口以匹配窗口的宽高
        glViewport(0, 0, w, h);

        // 切换到投影矩阵模式
        glMatrixMode(GL_PROJECTION);

        // 重置当前矩阵为单位矩阵，为接下来的投影设置做准备
        glLoadIdentity();

        // 计算窗口的宽高比
        float aspect = (float)w / (float)h;

        // 计算基于缩放因子的正交投影的X轴和Y轴范围
        float baseX = BASE_ORTHO_X * zoom;
        float baseY = BASE_ORTHO_Y * zoom;

        // 根据宽高比设置正交投影矩阵
        if (aspect >= 1.0F) {
            // 当宽高比大于等于1时，保持宽度方向的正交投影范围
            gluOrtho2D(-baseX * aspect, baseX * aspect, -baseY, baseY);
        } else {
            // 当宽高比小于1时，保持高度方向的正交投影范围
            gluOrtho2D(-baseX, baseX, -baseY / aspect, baseY / aspect);
        }

        // 切换回模型视图矩阵模式，以进行模型变换操作
        glMatrixMode(GL_MODELVIEW);
    }

    /**
     * @brief 键盘事件回调
     * @param key 按键
     * @param x,y 鼠标位置
     */
    void keyboard(unsigned char key, int x, int y) {
        stl::ignored_unused(x, y);

        if (key == ' ' || key == 'p' || key == 'P') {
            paused = !paused;
        } else if (key == ESC_KEY) {
            exit(0);
        } else if (key == '+') {
            speed += SPEED_STEP;
            speed = std::min(speed, SPEED_MAX);
        } else if (key == '-') {
            speed -= SPEED_STEP;
            speed = std::max(speed, SPEED_MIN);
        }
    }

    /**
     * @brief 鼠标事件回调
     * @param button 按钮
     * @param state 状态
     * @param x,y 鼠标位置
     */
    static void mouseFunc(int button, int state, int x, int y) {
        stl::ignored_unused(x, y);
        if (state == GLUT_DOWN) {
            if (button == 3) {  // 滚轮上
                instance.zoom *= ZOOM_STEP_IN;
                instance.zoom = std::max(instance.zoom, ZOOM_MIN);
                instance.reshape(glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT));
                glutPostRedisplay();
            } else if (button == 4) {  // 滚轮下
                instance.zoom *= ZOOM_STEP_OUT;
                instance.zoom = std::min(instance.zoom, ZOOM_MAX);
                instance.reshape(glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT));
                glutPostRedisplay();
            }
        }
    }

    static void displayFunc() { instance.display(); }

    static void timerFunc(int value) {
        stl::ignored_unused(value);
        instance.update();
    }

    static void reshapeFunc(int w, int h) { instance.reshape(w, h); }

    static void keyboardFunc(unsigned char key, int x, int y) { instance.keyboard(key, x, y); }

private:
    SolarSystem() : paused(false), stopped(false), zoom(1.0F), speed(1.0F) {
        // 太阳
        sun = new Planet("Sun", 0.0F, 0.15F, 0.0F, 1.0F, 1.0F, 0.0F);
        // 九大行星
        planets.push_back(new Planet("Mercury", 0.3F, 0.03F, 4.7F, 0.7F, 0.7F, 0.7F));
        planets.push_back(new Planet("Venus", 0.4F, 0.04F, 3.5F, 1.0F, 0.8F, 0.2F));
        planets.push_back(new Planet("Earth", 0.6F, 0.08F, 3.0F, 0.2F, 0.2F, 1.0F));
        planets.push_back(new Planet("Mars", 0.8F, 0.06F, 2.4F, 1.0F, 0.3F, 0.3F));
        planets.push_back(new Planet("Jupiter", 1.0F, 0.12F, 1.3F, 1.0F, 1.0F, 0.5F));
        planets.push_back(new Planet("Saturn", 1.2F, 0.10F, 1.0F, 0.8F, 1.0F, 0.8F));
        planets.push_back(new Planet("Uranus", 1.4F, 0.09F, 0.7F, 0.5F, 1.0F, 1.0F));
        planets.push_back(new Planet("Neptune", 1.6F, 0.08F, 0.5F, 0.3F, 0.5F, 1.0F));
        planets.push_back(new Planet("Pluto", 1.8F, 0.02F, 0.3F, 0.8F, 0.8F, 1.0F));
        // 月球
        moon = new Planet("Moon", 0.0F, 0.025F, 2.0F, 0.8F, 0.8F, 0.8F);
        // 绑定月球到地球
        planets[2]->setMoon(moon, 0.13F);
    }

private:
    static SolarSystem   instance;  ///< 静态实例指针
    bool                 paused;    ///< 是否暂停
    bool                 stopped;   ///< 是否停止
    float                zoom;      ///< 当前缩放比例
    float                speed;     ///< 当前速度倍数
    Planet*              sun;       ///< 太阳
    std::vector<Planet*> planets;   ///< 行星列表
    Planet*              moon;      ///< 月球
};
SolarSystem SolarSystem::instance;

/**
 * @brief 主函数，程序入口。
 */
int main(int argc, char** argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_MULTISAMPLE);
    glutInitWindowSize(1000, 800);

    // 获取屏幕分辨率并设置窗口居中
    int screenWidth  = 0;
    int screenHeight = 0;
#ifdef __linux__
    Display* d = XOpenDisplay(nullptr);
    if (d != nullptr) {
        Screen* s    = DefaultScreenOfDisplay(d);
        screenWidth  = s->width;
        screenHeight = s->height;
        XCloseDisplay(d);
    }
#endif
    if (screenWidth > 0 && screenHeight > 0) {
        glutInitWindowPosition((screenWidth - 1000) / 2, (screenHeight - 800) / 2);
    } else {
        glutInitWindowPosition(100, 100);  // 获取失败时默认
    }

    glutCreateWindow("OpenGL Solar System Simulation");
    // 启用多重采样功能，以改善图形渲染中的抗锯齿效果
    glEnable(GL_MULTISAMPLE);
    // 启用线条抗锯齿功能，使绘制的线条边缘更加平滑
    glEnable(GL_LINE_SMOOTH);
    // 设置线条抗锯齿的提示为最高质量，确保最佳的渲染效果
    glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
    glEnable(GL_BLEND);
    // 设置 blending 函数，用于控制如何将源颜色与目标颜色混合
    // 参数 GL_SRC_ALPHA 表示源颜色的 alpha 值作为源因子
    // 参数 GL_ONE_MINUS_SRC_ALPHA 表示用 1 减去源颜色的 alpha 值作为目标因子
    // 这种组合常用于实现透明效果，确保渲染结果既考虑了源颜色的透明度，也考虑了背景颜色的贡献
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // 设置OpenGL实用工具库(GLUT)的显示回调函数，负责渲染场景
    glutDisplayFunc(SolarSystem::displayFunc);
    // 设置GLUT的窗口大小改变时的回调函数，用于更新视口
    glutReshapeFunc(SolarSystem::reshapeFunc);
    // 设置GLUT的键盘按下事件回调函数，用于处理键盘输入
    glutKeyboardFunc(SolarSystem::keyboardFunc);
    // 设置GLUT的鼠标点击事件回调函数，用于处理鼠标操作
    glutMouseFunc(SolarSystem::mouseFunc);
    // 设置GLUT的定时器回调函数，用于控制动画或模拟的更新
    glutTimerFunc(TIMER_INTERVAL, SolarSystem::timerFunc, 0);
    // 初始化轨道顶点缓存
    initOrbitCache(ORBIT_SEGMENTS);
    glutMainLoop();
    return 0;
}
